{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Maintaining voltage setpoints via PandaModels.jl\n",
    "### PandaModels.jl: Interfacing PowerModels with pandapower\n",
    "\n",
    "This tutorial describes how to run the optimization for maintaining voltage setpoints via [PandaModels.jl](https://e2niee.github.io/PandaModels.jl/dev/).\n",
    "Maintaining voltage setpoits (MVSP) is an optimization problem which is exclusively defined in [PandaModels.jl](https://e2niee.github.io/PandaModels.jl/dev/) and\n",
    "needs the extra user defined parameters from pandapower.\n",
    "\n",
    "\n",
    "The general mathematical model of MVSP is defined as follows:\n",
    "\n",
    "\\begin{align}\n",
    "& \\underset{\\mathcal{X} = [q, ...]}{\\text{minimize}}\n",
    "& &  \\sum_{i\\in \\mathcal{BI}} [v_i - setpoint\\_v]^2 \\\\\n",
    "& \\text{subject to}\n",
    "& & g(\\mathcal{X})=0 \\\\\n",
    "& & & h(\\mathcal{X}) \\leq 0\n",
    "\\end{align}\n",
    "\n",
    "where $v_{i}$ is the voltage variable of bus $i$ in $\\mathcal{BI}$ which denotes the set of buses located at the DSO-TSO interfaces. The $g(\\mathcal{X})$ and $h(\\mathcal{X})$, denote equality and inequality constraints, respectively. The $\\mathcal{X}$ denotes the set of variables decisions, such as reactive power, $q$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Let's get started\n",
    "\n",
    "So here is an example of how it works. First, we create cigre grid with pv and wind DERs(distribution energy resource) from pandapower's network database."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pandapower.networks import create_cigre_network_mv\n",
    "from pandapower.run import runpp\n",
    "from pandapower.runpm import runpm_vstab\n",
    "from pandapower.create import create_poly_cost\n",
    "from copy import deepcopy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "net = create_cigre_network_mv(with_der=\"pv_wind\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "then we need to run powerflow to get the initial values:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "net.sgen.p_mw = net.sgen.p_mw * 8\n",
    "net.sgen.sn_mva = net.sgen.sn_mva * 8\n",
    "runpp(net)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's have a look at the grid we created with pandapower plotting module:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "import pandapower.plotting as plot\n",
    "plot.set_line_geodata_from_bus_geodata(net)\n",
    "plot.simple_plot(net)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "let's keep a copy of the net for further comparision:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "net_org = deepcopy(net)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "Although VD is not a function of PowerModels and is exclusively modeled in Pandamodels, PandaModels similar to PowerModels follows the data structures from [InfrastructureModels.jl](https://github.com/lanl-ansi/InfrastructureModels.jl).\n",
    " In this data structure DERs are not defined separately unlike pandapower,\n",
    " then the uncontrollable load and sgens are converted to load and controllable elements are converted to the generator.\n",
    "\n",
    "Accordingly, we need to set loads as uncontrollable elements and set DERs (sgens) as controllable elements."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "net.load['controllable'] = False\n",
    "net.sgen['controllable'] = True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "now, lets set the optimization boundaries:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "# lower and upper bounds for buses\n",
    "net.bus[\"max_vm_pu\"] = 1.1\n",
    "net.bus[\"min_vm_pu\"] = 0.9\n",
    "\n",
    "# lower and upper bounds for external grid\n",
    "net.ext_grid[\"max_q_mvar\"] = 10000.0\n",
    "net.ext_grid[\"min_q_mvar\"] = -10000.0\n",
    "net.ext_grid[\"max_p_mw\"] = 10000.0\n",
    "net.ext_grid[\"min_p_mw\"] = -10000.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "in VD we need to keep the active power value for DER and generators constant to the calculated value from power flow.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "# lower and upper bounds for DERs\n",
    "net.sgen[\"max_p_mw\"] = net.sgen.p_mw.values\n",
    "net.sgen[\"min_p_mw\"] = net.sgen.p_mw.values\n",
    "net.sgen[\"max_q_mvar\"] = net.sgen.p_mw.values * 0.328\n",
    "net.sgen[\"min_q_mvar\"] = -net.sgen.p_mw.values * 0.328\n",
    "\n",
    "# lower and upper bounds for generators\n",
    "net.gen[\"max_p_mw\"] = net.gen.p_mw.values\n",
    "net.gen[\"min_p_mw\"] = net.gen.p_mw.values\n",
    "net.gen[\"max_q_mvar\"] = 10000.0\n",
    "net.gen[\"min_q_mvar\"] = -10000.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "Let's set a high upper bound for line and transformers to avoid congestion issue:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "# lower and upper bounds for lines\n",
    "net.trafo[\"max_loading_percent\"] = 500.0\n",
    "net.line[\"max_loading_percent\"] = 500.0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "I also we could set costs for sgens, gens and external grids in order to not get warnings during data correctness in julia side."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "for idx in net.sgen.index:\n",
    "    create_poly_cost(net, idx, \"sgen\", 1.0)\n",
    "for idx in net.gen.index:\n",
    "    create_poly_cost(net, idx, \"gen\", 1.0)\n",
    "for idx in net.ext_grid.index:\n",
    "    create_poly_cost(net, idx, \"ext_grid\", 1.0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "Finally, lets add our user defined parameter \"setpoint_v\". at first we need to add extra column called \"pm_param/setpoint_v\" in bus data,\n",
    "then we set values for buses contain DERs(sgens):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "net.bus[\"pm_param/setpoint_v\"] = None\n",
    "net.bus.loc[net.sgen.bus, \"pm_param/setpoint_v\"] = 0.99"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now lets run an MVSP through PandaModels and look at the results (Note that the first time the function is called, Julia is started in the background, which may take some time):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "try:\n",
    "    runpm_vstab(net)\n",
    "except Exception as err:\n",
    "    print(err)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "Also, there more parameters and options that you can add as input while calling the Optimization Problem from PandaModles:\n",
    "\n",
    "| parameter | description | type | default |\n",
    "| :--- | :--- | :---: | :--- |\n",
    "| correct_pm_network_data | checks if network data is correct. If not tries to correct it | bool | True |\n",
    "| silence | Suppresses information and warning messages output by PowerModels | bool | True |\n",
    "| pm_model | PowerModels.jl model to use | str | \"ACPPowerModel\" |\n",
    "| pm_solver | \"main\" solver| str | \"ipopt\" |\n",
    "| pm_mip_solver | mixed integer solver| str | \"cbc\" |\n",
    "| pm_nl_solver | nonlinear solver| str | \"ipopt\" |\n",
    "| pm_tol | default desired convergence tolerance for solver to use | float | 1e-8 |\n",
    "| pm_log_level | solver log level in power models | int | 0 |\n",
    "| delete_buffer_file | If True, the .json file used by PandaModels will be deleted after optimization. | bool | True |\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "lets check the result values for voltage and reactive power at buses contains DERs before and after VD:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "# results from power flow\n",
    "net_org.res_bus.vm_pu[net_org.sgen.bus]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# results from VD\n",
    "net.res_bus.vm_pu[net.sgen.bus]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "as exected the results are close to the defined setpoints, and as we set the active power constants we expect to get different results from power flow for reactive power:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "# results from normal power flow\n",
    "net_org.res_sgen.q_mvar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "# results from MVSP\n",
    "net.res_sgen.q_mvar"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
